#include<iostream>
#include<string>
#include<unordered_map>
#include<cassert>
#include<cstdlib>
#include<cerrno>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<cstdarg>
#include"logMessage.hpp"
using std::cout;
using std::endl;
using std::string;
using std::unordered_map;

class udpServer
{
    public:
        udpServer(uint16_t port,string ip="")
            :_port(port)
            ,_ip(ip)
            ,_sockFd(-1)
            {} 
        ~udpServer()
        {}
        void Init()
        {
            _sockFd=socket(AF_INET,SOCK_DGRAM,0);
            if(_sockFd<0)
            {
        logMessage(FATAL,"socket() failed:: %s : %d",strerror(errno),_sockFd);
                exit(1);
            }
            logMessage(DEBUG,"socket() success:: %d",_sockFd);
            
            struct sockaddr_in local;
            bzero(&local,sizeof(local));
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            local.sin_addr.s_addr=_ip.empty()?htonl(INADDR_ANY):inet_addr(_ip.c_str());

            if(bind(_sockFd,(const struct sockaddr*)&local,sizeof(local))==-1)
            {
                logMessage(FATAL,"bind() failed::%s : %d",strerror(errno),_sockFd);
                exit(2);
            }
            logMessage(DEBUG,"bind() success::%d",_sockFd);
        }
        void start()
        {
            char serBuffer[1024];
            while(1)
            {
                struct sockaddr_in peer;
                socklen_t peerlen=sizeof(peer);

                ssize_t s=recvfrom(_sockFd,serBuffer,sizeof(serBuffer)-1,0,(struct sockaddr*)&peer,&peerlen);
                if(s>0)
                {
                    serBuffer[s]=0;
                }
                else if(s==-1)
                {
                    logMessage(WARINING,"recvfrom() error::%s : %d",strerror(errno),_sockFd);
                    continue;
                }
                string peerIp=inet_ntoa(peer.sin_addr);
                uint16_t peerPort=ntohs(peer.sin_port);
                CheckOnlineUsers(peerIp,peerPort,peer);
                logMessage(NOTICE,"[%s:%d]# %s",peerIp.c_str(),peerPort,serBuffer);
                string infoUser(serBuffer);
                messageRount(peerIp,peerPort,infoUser);
            }   
        }
        void CheckOnlineUsers(string &ip,uint16_t port,struct sockaddr_in &peer)
        {
            string key=ip;
            key += ":";
            key += std::to_string(port);
            auto itUser=_users.find(key);
            if(itUser==_users.end())
            {
                _users.insert({key,peer});
            }
        }
        void messageRount(string &ip ,uint16_t port,string info)
        {
            string message="[";
            message += ip;
            message +=":";
            message +=std::to_string(port);
            message +="]";
            message +=info;
            for(auto &user : _users)
            {
                sendto(_sockFd,message.c_str(),message.size(),0,(struct sockaddr*)&(user.second),sizeof(user.second));
            }
        }
    private:
        uint16_t _port;
        string _ip;
        int _sockFd;
        unordered_map<string, struct sockaddr_in> _users;
};

static void Usage(const string proc)
{
    cout<<"Usage:\n\t"<<proc<<"port [ip]"<<endl;
}

int main(int argc,char* argv[])
{
    if(argc!=2&&argc!=3)
    {
        Usage(argv[0]);
        exit(3);
    }
    uint16_t port=atoi(argv[1]);
    string ip;
    if(argc==3)
    {
        ip=argv[2];
    }
    udpServer uSvr(port,ip);
    uSvr.Init();
    uSvr.start();
    return 0;

}


